package edu.vt.ece.rt.benchmark.bank;

import org.jgroups.blocks.ConnectionTableNIO.MyFuture;

import edu.vt.ece.rt.benchmark.BenchmarkThread;
import edu.vt.ece.rt.dstm.Context;
import edu.vt.ece.rt.dstm.ContextDelegator;
import edu.vt.ece.rt.dstm.QObject;
import edu.vt.ece.rt.dstm.TMProxy;
import edu.vt.ece.rt.quorum.ClustersManager;
import edu.vt.ece.rt.quorum.Resource;
import edu.vt.ece.rt.util.Logger.Logger;

/**
 * @author Sudhanshu
 */

public class BankAccount extends QObject {

	private Integer amount = 0;
	private String id;

	public BankAccount(String ID) {
		id = ID;
		TMProxy.getTMProxy().publishObject(this);
	}

	public BankAccount(){}		//For cloning
	/**To create Account with some money at start
	 * @param id
	 * @param amount2
	 */
	public BankAccount(String ID, int amount2) {
		id = ID;
		Logger.debug("Created 	 object with "+ID);
		amount = new Integer(amount2);		
		TMProxy.getTMProxy().publishObject(this);
		Resource.putQObject(getId(), this);
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public int getAmount() {
		return amount;
	}

	public void setAmount(int amt) {
		this.amount = amt;
	}


	public void deposit(int dollars) {
		amount = amount + dollars;
	}

	public boolean withdraw(int dollars) {
		amount = amount - dollars;
		return amount >= 0;
	}

	/**
	 * Provides the balance of given account
	 * 
	 * @return
	 */
	public long checkBalance() {
		return amount;
	}

	public static long totalBalanceNested(String accountNum, Context parentContext) {
		long result = 0;
		//		Logger.debug("Calculating totalBalance");

		for (int i = 0; i < 0x7fffffff; i++) {
			if (i > 0) {
				((BenchmarkThread)Thread.currentThread()).incrementAborts();
			}
			Context context = ContextDelegator.getNestedContext();
			context.setParent(parentContext);
			context.setDescription("total balance for one account");
			context.sanityNested();

			Logger.debug("----Started----- R Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {

				result = totalBalance(accountNum, context);
				
				if (TMProxy.getTMProxy().requestCommit(context.getContextId())) {
					//					Logger.debug("Total balance is "+result);
					context.unsetParent(parentContext);
					return result;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		Logger.fetal("Unable to complete transaction in given number of tries");
		return result;
	}


	/* to be only used for sanity */
	public static long getBalance(String accountNum, Context parentContext) {
			
		long result = 0;
		//		Logger.debug("Calculating totalBalance");

		for (int i = 0; i < 0x7fffffff; i++) {
			Context context = ContextDelegator.getNestedContext();
			context.setParent(parentContext);
			context.setDescription("total balance for one account");
			context.sanityNested();

			Logger.debug("----Started----- R Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {

				BankAccount account = (BankAccount) TMProxy.getTMProxy().read(
						accountNum, context.getContextId());

				result = account.checkBalance();

				Logger.debug("Account balance is" + account.amount);

				return result;
			} catch (Exception e) {
				e.printStackTrace();
			}

		}
		Logger.fetal("Unable to complete transaction in given number of tries");
		return result;
	}

	
	
	public static long totalBalance(String accountNum, Context c) throws Exception {

		BankAccount account = (BankAccount) TMProxy.getTMProxy().read(
				accountNum, c.getContextId());

		long balance = 0;
		for (int i = 0; i < Benchmark.calls; i++) 
			balance += account.checkBalance();

		Logger.debug("Account balance is" + account.amount);

		return balance;
	}

	public static long totalBalance(String accountNum1, String accountNum2,
			Context c) throws Exception {

		BankAccount account1 = (BankAccount) TMProxy.getTMProxy().read(
				accountNum1, c.getContextId());
		Logger.debug("Lets check read set of one");
		Resource.displayObjects(c);

		BankAccount account2 = (BankAccount) TMProxy.getTMProxy().read(
				accountNum2, c.getContextId());
		Logger.debug("Lets check read set of two");
		Resource.displayObjects(c);

		long balance = 0;
		for (int i = 0; i < Benchmark.calls; i++) 
			balance += account1.checkBalance();

		Logger.debug("Account1 balance is" + account1.amount);

		for (int i = 0; i < Benchmark.calls; i++)
			balance += account2.checkBalance();
		Logger.debug("Account2 balance is" + account2.amount);
		return balance;

	}

	/**
	 * Get the total balance in given to accounts
	 * 
	 * @param accountNum1
	 *            - Id of bank account number 1
	 * @param accountNum2
	 *            - Id of bank account number 2
	 */
	public static long totalBalanceNested(String accountNum1, String accountNum2, Context parentContext) {
		long result = 0;
		Logger.debug("Calculating totalBalance for "+accountNum1 + " "+accountNum2);
		for (int i = 0; i < 0x7fffffff; i++) {

			if (i > 0) {
				((BenchmarkThread)Thread.currentThread()).incrementAborts();
			}

			Context context = ContextDelegator.getNestedContext();
			context.setParent(parentContext);
			context.setDescription("total balance for two accounts "+accountNum1 + " "+accountNum2);
			context.sanityNested();

			Logger.debug("----Started----- R Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {

				result = totalBalance(accountNum1, accountNum2, context);
				
				if (TMProxy.getTMProxy().requestCommit(context.getContextId())) {
					Logger.debug("Total balance is "+result);
					context.unsetParent(parentContext);
					Resource.displayObjects(context);
					return result;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		Logger.fetal("Unable to complete transaction in given number of tries");
		return result;
	}

	/**
	 * Get the total balance in given to accounts
	 * 
	 * @param accountNum1
	 *            - Id of bank account number 1
	 * @param accountNum2
	 *            - Id of bank account number 2
	 */
	public static long rootTransaction1(String accountNum1, String accountNum2) {

		long result = 0;
		/*
		 * Explicitly setting current as null for root transaction  
		 */
		Context parentContext = null;

		for (int i = 0; i < 0x7fffffff; i++) {

			if (i > 0) {
				((BenchmarkThread)Thread.currentThread()).incrementAborts();
			}

			Context context = ContextDelegator.getNestedContext();
			context.setParent(parentContext);
			context.setDescription("starting root transaction");
			context.sanityNested();

			Logger.debug("----Started----- R Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {
				//				if (ClustersManager.baseClusterId ==  1 || ClustersManager.baseClusterId ==  2)
				//					Logger.debug("Do transfer for this nodes");
				transferNested(accountNum1, accountNum2, 10, context);
				transferNested(accountNum1, accountNum2, 10, context);
				transferNested(accountNum1, accountNum2, 10, context);
				//				} else {
				//					Thread.sleep(3000);
				//				}
				//				transfer(accountNum1, accountNum2, 10);
				//				BankAccount account1 = (BankAccount) TMProxy.getTMProxy().write(
				//						accountNum1, context.getContextId());
				//				BankAccount account2 = (BankAccount) TMProxy.getTMProxy().write(
				//						accountNum2, context.getContextId());
				//				Logger.debug("Money in account 1 "+account1.checkBalance());
				//				Logger.debug("Money in account 2 "+account2.checkBalance());
				totalBalanceNested(accountNum1, accountNum2, context);
				//				transfer(accountNum1, accountNum2, 10);
				//				totalBalance(accountNum1, accountNum2);

				if (TMProxy.getTMProxy().requestCommit(context.getContextId())) {
					Logger.debug("Nested result is "+result);
					context.unsetParent(parentContext);
					return result;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		Logger.fetal("Unable to complete transaction in given number of tries");
		return result;
	}

	/**
	 * Get the total balance in two accounts, transfer money from one to other, update the global account
	 * 
	 * @param accountNum1
	 *            - Id of bank account number 1
	 * @param accountNum2
	 *            - Id of bank account number 2
	 */
	public static long rootTransaction2(String accountNum1, String accountNum2) {

		long result = 0;
		/*
		 * Explicitly setting current as null for root transaction  
		 */
		Context parentContext = null;

		//		for (int i = 0; i < 0x7fffffff; i++) {
		for (int i = 0; i < 1; i++) {
			if (i > 0) {
				((BenchmarkThread)Thread.currentThread()).incrementAborts();
				//				System.out.println("Starting root transaction on ["+ClustersManager.baseClusterId+"] for "+i+" th time");

			}

			Context context = ContextDelegator.getNestedContext();
			context.setParent(parentContext);
			context.setDescription("starting root transaction");
			context.sanityNested();

			Logger.debug("----Started----- R Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {
				transferNested(accountNum1, accountNum2, 10, context);
				Resource.displayObjects(context);
				totalBalanceNested(accountNum1, accountNum2, context);
				Resource.displayObjects(context);
				/* trivial because transfers are within banks */
				//					System.out.println("Going to update global data on ["+ClustersManager.baseClusterId+"]");
				updateGlobalAccount(accountNum1, context);
				updateGlobalAccount(accountNum2, context);
				Resource.displayObjects(context);
				//					System.out.println("Finished updating global data on ["+ClustersManager.baseClusterId+"]");

				if (TMProxy.getTMProxy().requestCommit(context.getContextId())) {
					Logger.debug("Nested result is "+result);
					context.unsetParent(parentContext);
					//					System.out.println("Committing this root transaction on ["+ClustersManager.baseClusterId+"]");
					return result;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		Logger.fetal("Unable to complete transaction in given number of tries");
		return result;
	}


	/**
	 * Transfers the given amount of money between different accounts
	 * 
	 * @param id1
	 *            - Bank Account Number 1 ID
	 * @param id2
	 *            - Bank Account Number 2 ID
	 * @param transfer
	 *            - Amount to be transferred
	 */
	public static void transferNested(String accountNum1, String accountNum2,
			int transfer, Context parentContext) {

		for (int i = 0; i < 0x7fffffff; i++) {

			if (i > 0) {
				((BenchmarkThread)Thread.currentThread()).incrementAborts();
			}

			Context context = ContextDelegator.getNestedContext();
			context.setParent(parentContext);
			context.setDescription("transfer from "+accountNum1+" account to "+accountNum2);
			context.sanityNested();

			Logger.debug("-----Started ----- W Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {
				transfer(accountNum1, accountNum2, transfer, context);
				
				Logger.debug("Before sending commit request");
				Resource.displayObjects(context);
				if (TMProxy.getTMProxy().requestCommit(context.getContextId())) {
					context.unsetParent(parentContext);
					Resource.displayObjects(context);
					return;
				}
			} catch (Exception e) {			

			}

			//			try {
			//				Thread.sleep((ClustersManager.baseClusterId+1)*100);
			//			} catch (InterruptedException e) {
			//				e.printStackTrace();
			//			}
		}
		Logger.fetal("Unable to complete transaction in given number of tries");
	}

	/**
	 * Update the global account with new balances
	 * 
	 */
	public static void updateGlobalAccount(String account, Context parentContext) {

		for (int i = 0; i < 0x7fffffff; i++) {

			if (i > 0) {
				((BenchmarkThread)Thread.currentThread()).incrementAborts();

				if (i > 100) {
					System.out.println("I am stuck for "+ClustersManager.baseClusterId);
				}

				try {

					Logger.debug("Sleeping for "+i*(ClustersManager.baseClusterId+1)*1000L);
					Thread.sleep(i*(ClustersManager.baseClusterId+1)*100L);
					Logger.debug("Waking up from sleep ["+ClustersManager.baseClusterId+"]");
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

			}


			Context context = ContextDelegator.getNestedContext();
			context.setParent(parentContext);
			context.setDescription("update global account ");
			context.sanityNested();

			Logger.debug("-----Started ----- W Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {
				String id = account.substring(0, account.indexOf('-')) + "-global";
				//				System.out.println("Opening "+id+ " for updating global data");
				BankAccount global = (BankAccount)TMProxy.getTMProxy().write(id, context.getContextId());

				global.setAmount(global.getAmount() + 0);

				if (TMProxy.getTMProxy().requestCommit(context.getContextId())) {
					context.unsetParent(parentContext);
					Resource.displayObjects(context);
					return;
				}
			} catch (Exception e) {			

			}

		}
		Logger.fetal("Unable to complete transaction in given number of tries");
	}

	public static void transfer(String accountNum1, String accountNum2,
			int amount, Context c) throws Exception {

		BankAccount account1 = (BankAccount) TMProxy.getTMProxy().write(
				accountNum1, c.getContextId());
		BankAccount account2 = (BankAccount) TMProxy.getTMProxy().write(
				accountNum2, c.getContextId());

		Logger.debug("Read value ac1="+account1.amount + " ac2="+account2.amount);
		for (int i = 0; i < Benchmark.calls; i++)
			account1.withdraw(amount);

		for (int i = 0; i < Benchmark.calls; i++)
			account2.deposit(amount);


		totalBalanceNested(accountNum1, accountNum2, c);
		Logger.debug("Written value ac1="+account1.amount + " ac2="+account2.amount);
	}

	public static void flatNestedTransfer(String accountNum1, String accountNum2) throws Exception {

		int amount = 10;
		for (int i = 0; i < 0x7fffffff; i++) {

			if (i > 0)
				((BenchmarkThread)Thread.currentThread()).incrementAborts();
			
			Context context = ContextDelegator.getNestedContext();
			//			context.setParent(null);
			context.setDescription("nested transaction");
			context.sanityNested();

			if (i > 0) {
				((BenchmarkThread)Thread.currentThread()).incrementAborts();

				if (i > 100) {
					System.out.println("I am stuck for "+ClustersManager.baseClusterId);
				}

				try {
					Logger.debug("Sleeping for "+i*(ClustersManager.baseClusterId+1)*1000L);
					Thread.sleep(i*(ClustersManager.baseClusterId+1)*100L);
					Logger.debug("Waking up from sleep ["+ClustersManager.baseClusterId+"]");						
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}	
			
			try {

				BankAccount account1 = (BankAccount) TMProxy.getTMProxy().write(accountNum1, context.getContextId());
				BankAccount account2 = (BankAccount) TMProxy.getTMProxy().write(accountNum2, context.getContextId());

				Logger.debug("Read value ac1="+account1.amount + " ac2="+account2.amount);

				account1.withdraw(amount);
				account2.deposit(amount);

				String id1 = accountNum1.substring(0, accountNum1.indexOf('-')) + "-global";
				BankAccount global1 = (BankAccount)TMProxy.getTMProxy().write(id1, context.getContextId());
				global1.setAmount(global1.getAmount() - 10);

				String id2 = accountNum2.substring(0, accountNum2.indexOf('-')) + "-global";
				BankAccount global2 = (BankAccount)TMProxy.getTMProxy().write(id2, context.getContextId());
				global2.setAmount(global2.getAmount() + 10);

				Logger.debug("Written value ac1="+account1.amount + " ac2="+account2.amount);
				
				if (TMProxy.getTMProxy().requestCommit(context.getContextId())) {
//					context.unsetParent(parentContext);
//					Resource.displayObjects(context);
					return;
				}
			} catch (Exception e) {			

			}

		}
	}
	
	@Override
	public String toString() {
		return getId() + "---" + hashCode();
	}

	@Override
	public QObject clone() {
		BankAccount bac = new BankAccount();
		bac.amount = amount;
		bac.id = id;
		bac.setVersion(getVersion());
		return bac;
	}
}